#include "TCPConnection.h"
#include "../Stringops/StringOps.h"
#include "../packet_headers/iphdr.h"
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <map>
using namespace std;

#define READ_BLOCK_SIZE 256

bool is_ip(std::string _host) {
	std::vector<std::string> parts = split(_host, '.');
	for(unsigned int i = 0; i < parts.size(); i++)
		if(!is_numeric(parts[i]))
			return false;
	return true;
}

sockaddr_in TCPConnection::host_to_addr(std::string _host, unsigned short _port) {
	struct sockaddr_in output;
	struct hostent * he;

	output.sin_family = AF_INET;
	output.sin_port = htons(_port);

	if(is_ip(_host)) {
		// Get host by addr
		output.sin_addr.s_addr = inet_addr(_host.c_str());
	} else {
		// Get host by name
		he = gethostbyname(_host.c_str());
		if(he == 0) {
			std::string err_msg = "";
			if(errno == HOST_NOT_FOUND) {
				err_msg = "Host not found.";
			}
			else if(errno == NO_ADDRESS) {
				err_msg = "No address associated with hostname.";
			}
			else {
				err_msg = "Could not resolve hostname.";
			}
			printf("Error: %s\n", err_msg.c_str());
			output.sin_addr.s_addr = null;
			return output;
		}
		output.sin_addr.s_addr = inet_addr(he->h_addr);
	}

	return output;
}

void TCPConnection::handle_client_connection(){
	int maxfdp = sock;
	int rset;
	char input[BUFFER_SIZE_CLI];
	char* message;
	fd_set fdcli_read;
	int hdr_type;
	int is_initialed = 0;
	
	//loop
	while(1){
		FD_ZERO(&fdcli_read);
		FD_SET(sock, &fdcli_read);
		FD_SET(0, &fdcli_read);
		rset = 0;
		hdr_type = 0;
		//Send High level initial message to server
		if(is_initialed == 0){
			char _burst_time[1];
			char _round_time[1];
			sprintf(_burst_time,"%d", burst_time);
			sprintf(_round_time,"%d", round_time);
			Packet p = build_packet(LOW_LEVEL_INI,BURST_TIME,_burst_time,ROUND_TIME,_round_time);
			buffer result = encode(p);
			rset = send(sock, result.mem, result.size,0);
			if(rset<=0){
				printf("Cannot send your LOW LEVEL initial message!\n");
				close_connection();
				exit(1);
			}else{
				is_initialed = 1;
			}
			memset(&input[rset],'\0',1);
		}


		// execute select in client
		rset = select(maxfdp+1, &fdcli_read, NULL, NULL, NULL);
		memset(&input,'\0',sizeof(input));
		/*if(rset <0){
			perror("select");
			exit(1);
		}*/
	
		// check read fdset
		if(FD_ISSET(sock,&fdcli_read)){
			rset = recv(sock, input, BUFFER_SIZE_CLI,0);
			if(rset<=0){
				printf("Your client has been closed by server!\n");
				close_connection();
				exit(1);//stop program
			}else{
				if(rset<BUFFER_SIZE_CLI)
					memset(&message,0,sizeof(message));
					hdr_type;
					hdr_type = gettype(input);//get header type
					if(hdr_type == LOW_LEVEL_ACK){
						char* _serv_raw_port = getmessage(input);
						serv_raw_port = atoi(_serv_raw_port);
						struct sockaddr_in serv, cli;
						socklen_t serv_len = sizeof(serv);
						socklen_t cli_len = sizeof(cli);
						getsockname(sock, (struct sockaddr *)&cli, &cli_len);
						getpeername(sock, (struct sockaddr *)&serv, &serv_len);
						strcpy(serv_ip, inet_ntoa(serv.sin_addr));
						strcpy(cli_ip, inet_ntoa(cli.sin_addr));
						//RawConnection cli_raw_conn(cli_ip,ip.c_str(),cli_raw_port,serv_raw_port,0);
						RawConnection _ctcp_raw(cli_ip,serv_ip,cli_raw_port,
							serv_raw_port,IPPROTO_TCP);
						RawConnection _cudp_raw(cli_ip,serv_ip,cli_raw_port+1,
							serv_raw_port+1,IPPROTO_UDP);
						RawConnection _cicmp_raw(cli_ip,serv_ip,cli_raw_port+2,
							serv_raw_port+2,IPPROTO_ICMP);
						ctcp_raw = _ctcp_raw;
						cudp_raw = _cudp_raw;
						cicmp_raw = _cudp_raw;
						if(ctcp_raw.sockfd==null||cudp_raw.sockfd==null||cicmp_raw.sockfd==null) {
							printf("Failed to open RAW CONNECTION!\n");
							close_connection();
							exit(1);//stop program
						}else{
							char  _cli_raw_port[1];
							sprintf(_cli_raw_port,"%d", cli_raw_port);
							Packet p = build_packet(BURST_READY,RAW_PORT,_cli_raw_port);
							buffer ready = encode(p);
							rset = send(sock, ready.mem, ready.size,0);
							if(rset<=0){
								printf("Cannot send your BURST READY message!\n");
								close_connection();
								exit(1);
							}
							memset(&input[rset],'\0',1);
						}
					}else if(hdr_type == BURST_READY){
						byte_list=create_byte_list();
						rset = round_burst(ctcp_raw, cudp_raw, cicmp_raw, byte_list, byte_it);
					}else if(hdr_type == STATISITC){
						//DO SOME MATHMATIC TO ANALYZE DATA!!!!
					}
			}
		}
	}
}

int TCPConnection::round_burst(RawConnection ctcp_raw, RawConnection cudp_raw, RawConnection cicmp_raw,
			map<int, byte*> byte_list, map<int, byte*>::iterator byte_it){
	for(int i = 0; i<burst_time; i++){
		for(int j = 0; j<round_time; j++){
			//build the random list each round
			int random_list[RAW_TYPE_NUMBER];
			for(int m=0; m<RAW_TYPE_NUMBER; m++){
				random_list[m]=m+1;	
			}
			for(int n=(RAW_TYPE_NUMBER-1); n>=0; n--){
				int l = rand()%(n+1);
				int k = random_list[l];
				random_list[l] = random_list[n];
				random_list[n] = k;
			}
			for(int q=0; q<RAW_TYPE_NUMBER; q++){
				byte_it = byte_list.find(random_list[q]);
				byte* raw_byte = byte_it->second;
				struct ipheader * judge = (struct ipheader*) ((const char*)raw_byte);
				int _type = judge->id;
				printf("type: %d\n", byte_it->first);	
				//for(;;);
				if(_type==UDP_SNMP || _type==UDP_R1 || _type==UDP_R2){
					cudp_raw.send((const char*)raw_byte);	
				}else if(_type==ICMP_ECHO){
					cicmp_raw.send((const char*)raw_byte);
				}else{
					ctcp_raw.send((const char*)raw_byte);	
				}	
			}
		}
		printf("round end!!!\n");
		time_t first, second;
		first = time(NULL);
		
		//time control between two bursts
		while(1){ 
			second = time(NULL);
			double _delta = difftime(second, first);
			if(_delta > 1){
				byte_it = byte_list.find(CLI_END_SIGNAL);
				byte* sig = byte_it->second;
				ctcp_raw.send((const char*)sig);
				first = time(NULL);
				while(1){
					second = time(NULL);
					_delta = difftime(second, first);
					if(_delta > (delta_sec)){
						break;
					}
				}
				break;
			}
		}
		/*byte_it = byte_list.find(CLI_END_SIGNAL);
		byte* sig = byte_it->second;
		ctcp_raw.send((const char*)sig);
		first = time(NULL);
		while(1){
			second = time(NULL);
			double _delta = difftime(second, first);
			if(_delta > (delta_sec)){
				break;
			}
		}*/
	}

	printf("end of burst!!!!!!!!!!!!!\n");
	
	return 0;
}

map<int, byte*> TCPConnection::create_byte_list(){
	map<int, byte*> _byte_list;
	char _data[10] = "rawpacket";
	char _tos = IP_TOS_FILL(0, false, false, false);
	char _urgent = IP_TOS_FILL(1, true, true, true);
	char flags = TCP_Flags(false, false, false, false, false, false);
	byte * icmp_echo_reply = icmp_packet(cli_ip, serv_ip,
		_tos, ICMP_ECHO,
		0, 0,
		100, 200,
		0);
	byte * udp_snmp = udp_packet(cli_ip, serv_ip,
		_tos, UDP_SNMP,
		_data, 1000,
		cli_raw_port+1, 161,
		0);
	byte * udp_r1 = udp_packet(cli_ip, serv_ip,
		_tos, UDP_R1,
		_data, 1000,
		cli_raw_port+1, 38523,
		0);
	byte * udp_r2 = udp_packet(cli_ip, serv_ip,
		_tos, UDP_R2,
		_data, 1000,
		cli_raw_port+1, 43822,
		0);
	byte * tcp_port20 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_PORT20,
		_data, 1000,
		cli_raw_port, 20,
		32, 32, flags,
		0);
	byte * tcp_port21 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_PORT21,
		_data, 1000,
		cli_raw_port, 21,
		64, 64, flags,
		0);
	byte * tcp_port23 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_PORT23,
		_data, 1000,
		cli_raw_port, 23,
		256, 256, flags,
		0);
	byte * tcp_port80 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_PORT80,
		_data, 1000,
		cli_raw_port, 80,
		128, 128, flags,
		0);
	byte * tcp_p2p1 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_P2P1,
		_data, 1000,
		cli_raw_port, 4661,
		4096, 4096, flags,
		0);
	byte * tcp_p2p2 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_P2P2,
		_data, 1000,
		cli_raw_port, 6881,
		2048, 2048, flags,
		0);
	byte * tcp_r1 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_R1,
		_data, 1000,
		cli_raw_port, 38523,
		1024, 1024, flags,
		0);
	byte * tcp_r2 = tcp_packet(cli_ip, serv_ip,
		_tos, TCP_R2,
		_data, 1000,
		cli_raw_port, 43822,
		512, 512, flags,
		0);
	byte * round_end_signal = tcp_packet(cli_ip, serv_ip,
		_urgent, CLI_END_SIGNAL,
		_data, 10,
		cli_raw_port, serv_raw_port,
		8192, 8192, flags,
		0);
	_byte_list.insert(pair<int,byte*>(ICMP_ECHO,icmp_echo_reply));
	_byte_list.insert(pair<int,byte*>(UDP_SNMP,udp_snmp));
	_byte_list.insert(pair<int,byte*>(UDP_R1,udp_r1));
	_byte_list.insert(pair<int,byte*>(UDP_R2,udp_r2));
	_byte_list.insert(pair<int,byte*>(TCP_PORT20,tcp_port20));
	_byte_list.insert(pair<int,byte*>(TCP_PORT21,tcp_port21));
	_byte_list.insert(pair<int,byte*>(TCP_PORT23,tcp_port23));
	_byte_list.insert(pair<int,byte*>(TCP_PORT80,tcp_port80));
	_byte_list.insert(pair<int,byte*>(TCP_P2P1,tcp_p2p1));
	_byte_list.insert(pair<int,byte*>(TCP_P2P2,tcp_p2p2));
	_byte_list.insert(pair<int,byte*>(TCP_R1,tcp_r1));
	_byte_list.insert(pair<int,byte*>(TCP_R2,tcp_r2));
	_byte_list.insert(pair<int,byte*>(CLI_END_SIGNAL,round_end_signal));
	return _byte_list;
}

void TCPConnection::close_connection() {
	close(sock);
}
